package com.chenfan.ccp.plug.http.handle;

import com.chenfan.ccp.plug.http.annotation.Consume;
import com.chenfan.ccp.plug.http.util.ConsumeUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.mvel2.MVEL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 组装实体类的拦截器
 */
@Aspect
public class ConsumeEntityHandler {


    private final Map<String, Object> cacheMap = new ConcurrentHashMap<>();
    private final Map<String, Map<String, Boolean>> fieldsCache = new ConcurrentHashMap<>();
    private final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * 拦截条件 第一个是方法 第二个是类拦截
     */
    @Pointcut("(execution(@com.chenfan.ccp.plug.http.annotation.Consume * *(..))) " +
            "|| (within(@com.chenfan.ccp.plug.http.annotation.Consume *))")
    public void consumeEntity() {

    }

    /**
     * 微服务具体调用拦截
     *
     * @param joinPoint
     * @return
     */
    @Around("consumeEntity()")
    public Object parseEntity(ProceedingJoinPoint joinPoint) throws Throwable {
        Class<?> newInstanceClass = getConsume(joinPoint);


        if (newInstanceClass == null) {
            log.warn("class has no @Consume annotation");
            return null;
        }

        String execClassName = joinPoint.getSignature().getDeclaringTypeName();
        String execMethodName = joinPoint.getSignature().getName();
        String key = execClassName + execMethodName;


        Object[] args = joinPoint.getArgs();

        String expression;
        Map<String, Object> valExec;
        Object execVal = cacheMap.get(key);
        if (execVal != null) {
            expression = (String) execVal;
            Map<String, Object> tmpMap = (Map<String, Object>) cacheMap.get(key + "_map");
            valExec = new HashMap<>(tmpMap.size() << 1);

            valExec.put("__obj", newInstanceClass.newInstance());
            int i = 0;
            for (Map.Entry<String, Object> entry : tmpMap.entrySet()) {

                valExec.put(entry.getKey(), args[i++]);
            }


        } else {
            String methodName = execMethodName;


            Class<?> reflectClass = Class.forName(execClassName);
            LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer =
                    new LocalVariableTableParameterNameDiscoverer();
            Map<String, Boolean> allFields = getAllFields(newInstanceClass);

            Method[] methods = reflectClass.getDeclaredMethods();
            Method paramMethod = null;
            for (Method method : methods) {

                if (method.getName().equals(methodName)) {
                    paramMethod = method;
                    break;
                }
            }

            if (paramMethod == null) {
                log.warn("@Consume annotation has no paramMethod find!!");
                return null;
            }
            StringBuilder builder = new StringBuilder("def  createObj(){");

            Map<String, Object> tmpMap = new LinkedHashMap<>();


            String[] methodNames = parameterNameDiscoverer.getParameterNames(paramMethod);
            int len = methodNames.length;
            for (int i = 0; i < len; i++) {

                if (allFields.containsKey(methodNames[i])) {

                    builder.append("if( ");
                    builder.append(methodNames[i]);
                    builder.append("!=null){ ");
                    builder.append("__obj.");
                    builder.append(methodNames[i]);
                    builder.append("=");
                    builder.append(methodNames[i]);
                    builder.append("};");

                }

                tmpMap.put(methodNames[i], args[i]);

            }
            builder.append("return __obj  } val = createObj();");

            cacheMap.put(key, builder.toString());
            valExec = new HashMap<>(cacheMap.size() << 1);
            valExec.put("__obj", newInstanceClass.newInstance());
            valExec.putAll(tmpMap);
            cacheMap.put(key + "_map", tmpMap);
            expression = builder.toString();
        }

        ConsumeUtil.setObj(MVEL.eval(expression, valExec));

        return joinPoint.proceed();

    }

    @After("consumeEntity()")
    public void remote() {
        ConsumeUtil.delObj();
    }

    private Map<String, Boolean> getAllFields(Class<?> aClass) {

        String key = aClass.getCanonicalName();

        Map<String, Boolean> fieldMap = fieldsCache.getOrDefault(key, new HashMap<>());

        if (!fieldMap.isEmpty()) {
            return fieldMap;
        }

        Field[] fieldArr = aClass.getDeclaredFields();

        for (Field field : fieldArr) {

            if (Modifier.isFinal(field.getModifiers())) {
                continue;
            }

            fieldMap.put(field.getName(), Boolean.TRUE);


        }


        while (!aClass.getCanonicalName().equals(Object.class.getCanonicalName())) {
            aClass = aClass.getSuperclass();
            fieldArr = aClass.getDeclaredFields();
            for (Field field : fieldArr) {
                if (Modifier.isProtected(field.getModifiers()) || Modifier.isProtected(field.getModifiers())
                        && !Modifier.isFinal(field.getModifiers())) {
                    fieldMap.put(field.getName(), Boolean.TRUE);
                }

            }


        }


        fieldsCache.put(key, fieldMap);

        return fieldMap;
    }

    private Class getConsume(ProceedingJoinPoint joinPoint) {
        MethodSignature joinPointObject = (MethodSignature) joinPoint.getSignature();
        Method method = joinPointObject.getMethod();

        if (method.isAnnotationPresent(Consume.class)) {
            Consume annotation = method.getAnnotation(Consume.class);
            return annotation.value();
        } else {
            Consume consume = AnnotationUtils.findAnnotation(joinPointObject.getMethod().getDeclaringClass(), Consume.class);
            if (consume != null) {
                return consume.value();
            } else {
                return null;
            }
        }
    }


}
